package gradle.common.feign.config;

import feign.Feign;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.Protocol;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClientProperties;
import org.springframework.cloud.openfeign.support.FeignEncoderProperties;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.net.ssl.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 提前把feign okhttp 定义好，防止子类程序 自己定义了okhttp 会覆盖原有配置，直接copy原配置既可以自定义配置
 * <p>
 * 参考类 {@link FeignAutoConfiguration.OkHttpFeignConfiguration}
 *
 * @ ConditionalOnClass(OkHttpClient.class )
 * @ ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
 * <p/>
 * <p>
 * DEBUG  feign.SynchronousMethodHandler#executeAndDecode(feign.RequestTemplate, feign.Request.Options)
 * 可以查看是否实际调用成功
 * <p/>
 */
@Slf4j
@AutoConfiguration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class,
        FeignEncoderProperties.class })
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttp3Configuration {

    private okhttp3.OkHttpClient okHttpClient;

    @Bean
//    @ConditionalOnMissingBean
    public okhttp3.OkHttpClient.Builder okHttpClientBuilder() {
        return new okhttp3.OkHttpClient.Builder();
    }

    @Bean
//    @ConditionalOnMissingBean(ConnectionPool.class)
    public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties) {
        int maxTotalConnections = httpClientProperties.getMaxConnections(); //default 200
        long timeToLive = httpClientProperties.getTimeToLive(); //default 900s
        TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
        return new ConnectionPool(maxTotalConnections, timeToLive, ttlUnit);
    }

    @Bean
    public okhttp3.OkHttpClient okHttpClient(okhttp3.OkHttpClient.Builder builder, ConnectionPool connectionPool,
                                             FeignHttpClientProperties httpClientProperties) {
        boolean followRedirects = httpClientProperties.isFollowRedirects();
        int connectTimeout = httpClientProperties.getConnectionTimeout(); //default 2000
        boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
        Duration readTimeout = httpClientProperties.getOkHttp().getReadTimeout(); //default 60s
        List<Protocol> protocols = httpClientProperties.getOkHttp().getProtocols().stream().map(Protocol::valueOf)
                .collect(Collectors.toList());
        if (disableSslValidation) {
            disableSsl(builder);
        }
        this.okHttpClient = builder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                .followRedirects(followRedirects).readTimeout(readTimeout).connectionPool(connectionPool)
                .protocols(protocols).build();
        return this.okHttpClient;
    }

    private void disableSsl(okhttp3.OkHttpClient.Builder builder) {
        try {
            X509TrustManager disabledTrustManager = new DisableValidationTrustManager();
            TrustManager[] trustManagers = new TrustManager[1];
            trustManagers[0] = disabledTrustManager;
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustManagers, new java.security.SecureRandom());
            SSLSocketFactory disabledSSLSocketFactory = sslContext.getSocketFactory();
            builder.sslSocketFactory(disabledSSLSocketFactory, disabledTrustManager);
            builder.hostnameVerifier(new TrustAllHostnames());
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            log.warn("Error setting SSLSocketFactory in OKHttpClient {}", ExceptionUtils.getStackTrace(e));
        }
    }

    @PreDestroy
    public void destroy() {
        if (this.okHttpClient != null) {
            this.okHttpClient.dispatcher().executorService().shutdown();
            this.okHttpClient.connectionPool().evictAll();
        }
    }

    /**
     * A {@link X509TrustManager} that does not validate SSL certificates.
     */
    class DisableValidationTrustManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }

    }

    /**
     * A {@link HostnameVerifier} that does not validate any hostnames.
     */
    class TrustAllHostnames implements HostnameVerifier {

        @Override
        public boolean verify(String s, SSLSession sslSession) {
            return true;
        }

    }

}